/**
 * MongoDB 操作封装，功能接口包括增删改查
 * 查询支持分页和不分页查询，并支持查询返回 Map 和 Bean对象；
 * 注意: MongoClient 自身已实现连接池
 *
 * by @author lms 2021.08.10
 * last update by lms 2021.08.13 增加查询排序功能
 */
package ms.db.mongodb;

import com.mongodb.BasicDBObject;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.bulk.BulkWriteResult;
import com.mongodb.client.*;
import com.mongodb.client.model.*;
import com.mongodb.client.result.*;
import ms.core.common.AppException;
import ms.core.common.Page;
import ms.core.common.consts.SysErr;
import ms.core.tool.*;
import ms.db.mongodb.DataConverter.*;
import ms.db.mongodb.QueryParam.SortField;
import org.bson.BsonValue;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.json.JsonWriterSettings;
import org.bson.types.ObjectId;

import java.util.*;

public class MongoCliDrv {
	private static String defaultConfigFile = "/config/mongo.properties";
	private static JsonWriterSettings dataConverter;
	private static Map<String, MongoConf> confs = new HashMap<>();		// 实例数据库连接配置参数
	private static Map<String, MongoClient> clients = new HashMap<>();	// 所有配置连接对象

	private String currentConfFile;										// 实例数据库连接配置文件

	/**
	 * 指定默认配置文件
	 * @param conf
	 */
	public static void initMongoConfigFile(String conf) {
		if (StrTool.isBlank(conf) || conf.equalsIgnoreCase(defaultConfigFile))
			return;

		defaultConfigFile = conf;
	}

	static void initDataConverter() {
		dataConverter = JsonWriterSettings.builder()
				.dateTimeConverter(new JsonDateTimeConverter())
				.objectIdConverter(new ObjectIdConverter())
				.decimal128Converter(new Decimal128Converter())
				.doubleConverter(new DoubleConverter())
				.int64Converter(new Int64Converter())
				.int32Converter(new Int32Converter())
				.binaryConverter(new BinaryConverter())
				.timestampConverter(new TimestampConverter())
				.build();
	}

	/**
	 * 获取默认配置文件"mongo.properties"连接实例
	 * @return
	 */
	public static synchronized final MongoCliDrv getInstance() {
		return new MongoCliDrv(defaultConfigFile);
	}

	/**
	 * 获取指定配置文件 confFile 连接实例
	 * @return
	 */
	public static synchronized final MongoCliDrv getInstance(String confFile) {
		return new MongoCliDrv(confFile);
	}

	/**
	 *  根据配置文件获取 MongoClient
	 *
	 * @param configFile
	 * @return
	 */
	private static synchronized MongoClient getMongoClient(String configFile) {
		return clients.get(configFile);
	}

	/**
	 *  设置配置文件对应的 MongoClient
	 *
	 * @param configFile
	 * @return
	 */
	private static synchronized void setMongoClient(String configFile, MongoClient cli) {
		clients.put(configFile, cli);
	}

	private static synchronized MongoConf getMongoConfig(String configFile) {
		MongoConf config = confs.get(configFile);
		if (config!=null)
			return config;

		try {
			config = new MongoConf();

			PropTool tool = PropTool.getInstance(configFile);
			config.setUri(tool.getValue("uri", null));
			config.setDbName(tool.getValue("dbName", ""));

			confs.put(configFile, config);

			return config;
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			return null;
		}
	}

	MongoCliDrv(String configFile) {
		this.currentConfFile = configFile;

		// 读取mongo客户端连接配置
		MongoConf conf = getMongoConfig(currentConfFile);
		if (conf==null) {
			Log4j2Tool.error(MongoCliDrv.class, "MongoDB连接配置文件未指定或不存在！");
			return;
		}

		MongoClient cli = getMongoClient(currentConfFile);
		if (cli!=null) return;

		// 初始化数据转换对象
		initDataConverter();

		try {
			MongoClientSettings setting = MongoClientSettings.builder()
										  .applyConnectionString(new ConnectionString(conf.getUri()))
										  .build();
			cli = MongoClients.create(setting);

			setMongoClient(currentConfFile, cli);
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_CONNECT_ERR, "MongoDB 数据库连接失败!", e);
		}
	}

	public void close() {
		try {
			MongoClient cli = getMongoClient(currentConfFile);
			if (cli!=null) {
				cli.close();
			}
			cli = null;
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
		}
	}

	private MongoCollection<Document> getCollectionObject(String collectionName) {
		try {
			MongoConf conf = getMongoConfig(currentConfFile);
			MongoClient cli = getMongoClient(currentConfFile);

			MongoDatabase db = cli.getDatabase(conf.getDbName());
			MongoCollection<Document> collection = db.getCollection(collectionName);
			return collection;
		} catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_TABLE_NOT_FOUND, "访问的文档不存在!", e);
		}
	}

	/**
	 * map 里含_id字段，转ObjectId
	 *
	 * @param <T>
	 * @param value
	 * @return
	 */
	public static void convertMapId(Map<String, Object> map) {
		if (map.get("_id")!=null) {
			String id = map.get("_id").toString();
			map.put("_id", new ObjectId(id));
		}
	}

	/**
	 * 对象转 document
	 *
	 * @param <T>
	 * @param value
	 * @return
	 */
	public static <T> Document toDocument(T value) {
		Document doc;
		if (value instanceof Document) {
			doc = (Document) value;
		} else if (value instanceof BasicDBObject){
			doc = new Document((BasicDBObject)value);
		} else {
			Map<String, Object> map = JsonTool.objectToMap(value, false);
			convertMapId(map);

			doc = new Document(map);
		}

		return doc;
	}

	/**
	 * 从插入返回对象，获取记录Id
	 *
	 * @param result
	 * @return
	 */
	public static ObjectId getInsertId(InsertOneResult result) {
		if (result==null)
			return null;

		return result.getInsertedId().asObjectId().getValue();
	}

	/**
	 * 从批量插入返回对象，获取记录Id列表
	 *
	 * @param result
	 * @return
	 */
	public static List<ObjectId> getInsertIdList(InsertManyResult result) {
		List<ObjectId> list = new ArrayList<>();
		if (result==null)
			return list;

		for(Map.Entry<Integer, BsonValue> entry: result.getInsertedIds().entrySet()) {
			list.add(entry.getValue().asObjectId().getValue());
		}

		return list;
	}

	/**
	 * 写入单条数据
	 *
	 * @param collectionName 集合名称
	 * @param value 写入数据对象，可以是json字符串、JavaBean、Map
	 * @return
	 */
	public <T> InsertOneResult insertDocument(String collectionName, T value) {
		try {
			MongoCollection<Document> collection = getCollectionObject(collectionName);
			Document doc = toDocument(value);
			return collection.insertOne(doc);
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, "数据写入文档失败!", e);
		}
	}

	/**
	 * 批量写入数据
	 *   批量写入时,如果碰到主键冲突,将抛出异常.并且未冲突前的数据将成功写入数据库.
	 *
	 * @param collectionName
	 * @param values
	 * @return
	 */
	public <T> InsertManyResult insertDocuments(String collectionName, List<T> values) {
		try {
			MongoCollection<Document> collection = getCollectionObject(collectionName);
			int size = values.size();
			List<Document> list = new ArrayList<>();
			for(int i=0; i<size; i++) {
				list.add(toDocument(values.get(i)));
			}
			return collection.insertMany(list);
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, "数据批量写入文档失败!", e);
		}
	}

	/**
	 * 条件 Map 参数转 Bson "and" 过滤对象
	 *
	 * @param cdt
	 * @return
	 */
	private Bson mapToFilter(Map<String, Object> cdt){
		if (cdt==null || cdt.size()==0)
			return null;

		List<Bson> filters = new ArrayList<>();
		for(Map.Entry<String, Object> entry: cdt.entrySet()) {
			if (entry.getKey().equalsIgnoreCase("_id"))
				filters.add(Filters.eq(entry.getKey(), new ObjectId(entry.getValue().toString())));
			else
				filters.add(Filters.eq(entry.getKey(), entry.getValue()));
		}
		return Filters.and(filters);
	}

	/**
	 * 查询场景下的 sort 参数转 Bson
	 *
	 * @param sorts
	 * @return
	 */
	public Bson toQuerySort(List<SortField> sorts){
		if (sorts==null || sorts.size()==0)
			return null;

		//处理排序
		BasicDBObject sort = new BasicDBObject();
		if (sorts!=null && sorts.size()>0) {
			for(SortField sortField: sorts) {
				sort.append(sortField.getFieldName(), sortField.getSort().value());
			}
		}

		return sort;
	}

	/**
	 * 聚合场景下的 sort 参数转 Bson
	 *
	 * @param sorts
	 * @return
	 */
	public Bson toGroupSort(List<SortField> sorts){
		Bson ret = toQuerySort(sorts);

		return (ret==null ? null : new BasicDBObject("$sort", ret));
	}

	/**
	 * 计算页数
	 * @param count
	 * @param pagesize
	 * @return
	 */
	private int calculatePage(long count, int pagesize) {
		return Math.round((count+pagesize-1)/pagesize);
	}

	/**
	 * 删除文档数据接口
	 *
	 * @param collectionName 集合名称
	 * @param delFilter 删除条件
	 * @return
	 */
	public long deleteDocument(String collectionName, Bson delFilter) {
		try {
			MongoCollection<Document> collection = this.getCollectionObject(collectionName);
			DeleteResult ret = collection.deleteMany(delFilter);
			return ret.getDeletedCount();
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, "删除文档记录失败!", e);
		}
	}

	/**
	 * 根据条件(key=value)并关系批量删除
	 * @param collectionName
	 * @param cdt
	 * @return
	 */
	public long deleteDocument(String collectionName, Map<String, Object> cdt) {
		try {
			MongoCollection<Document> collection = this.getCollectionObject(collectionName);

			Bson filter = this.mapToFilter(cdt);
			DeleteResult ret = collection.deleteMany(filter);
			return ret.getDeletedCount();
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, "删除文档记录失败!", e);
		}
	}


	/**
	 * 根据objectId删除
	 * @param collectionName
	 * @param id
	 * @return
	 */
	public long deleteDocument(String collectionName, String id) {
		try {
			MongoCollection<Document> collection = this.getCollectionObject(collectionName);
			DeleteResult ret = collection.deleteOne(Filters.eq("_id", new ObjectId(id)));
			return ret.getDeletedCount();
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, "删除文档记录失败!", e);
		}
	}

	/**
	 * 删除集合
	 * @param collectionName
	 * @return
	 */
	public boolean dropCollection(String collectionName) {
		try {
			MongoCollection<Document> collection = this.getCollectionObject(collectionName);
			collection.drop();
			return true;
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, "删除文档失败!", e);
		}
	}

	/**
	 * 压缩集合
	 * @param collectionName
	 * @param force 在副本集primary上执行需要加 force 选项
	 * @return
	 */
	public boolean compactCollection(String collectionName, Boolean force) {
		try {
			MongoConf conf = getMongoConfig(currentConfFile);
			MongoClient cli = getMongoClient(currentConfFile);

			MongoDatabase db = cli.getDatabase(conf.getDbName());
			Document doc = new Document();
			doc.append("compact", collectionName);
			if (force!=null) doc.append("force", force);
			db.runCommand(doc);
			return true;
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, "文档压缩失败!", e);
		}
	}

	/**
	 * 判断集合是否存在
	 * @param collectionName
	 * @return
	 */
	public boolean collectionExists(String collectionName) {
		try {
			MongoConf conf = getMongoConfig(currentConfFile);
			MongoClient cli = getMongoClient(currentConfFile);

			MongoDatabase db = cli.getDatabase(conf.getDbName());
			MongoIterable<String> collections = db.listCollectionNames();
			if (collections==null) return false;

			for(String item: collections) {
				if (item.equalsIgnoreCase(collectionName)) {
					return true;
				}
			}
			return false;
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, "判断文档是否存在失败!", e);
		}
	}

	/**
	 * 创建集合
	 *
	 * @param collectionName
	 * @return
	 */
	public boolean createCollection(String collectionName) {
		if (collectionExists(collectionName))
			throw new AppException(SysErr.DB_SQL_ERR, collectionName+"已存在.");

		try {
			MongoConf conf = getMongoConfig(currentConfFile);
			MongoClient cli = getMongoClient(currentConfFile);

			MongoDatabase db = cli.getDatabase(conf.getDbName());
			db.createCollection(collectionName);
			return true;
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, collectionName+"集合创建失败!", e);
		}
	}

	/**
	 * 创建集合索引
	 *
	 * @param collectionName
	 * @param keys
	 * @return
	 */
	public boolean createIndex(String collectionName, Bson keys, IndexOptions options) {
		if (!collectionExists(collectionName))
			throw new AppException(SysErr.DB_SQL_ERR, collectionName+"不存在.");

		try {
			MongoCollection<Document> collection = getCollectionObject(collectionName);
			collection.createIndex(keys, options);
			return true;
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, collectionName+"创建集合索引失败!", e);
		}
	}

	/**
	 * 删除集合索引
	 *
	 * @param collectionName
	 * @param keys 按创建索引时的字段列表，如 {"name":1,"age":1}
	 * @return
	 */
	public boolean dropIndex(String collectionName, Bson keys) {
		if (!collectionExists(collectionName))
			throw new AppException(SysErr.DB_SQL_ERR, collectionName+"不存在.");

		try {
			MongoCollection<Document> collection = getCollectionObject(collectionName);
			collection.dropIndex(keys);
			return true;
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, collectionName+"删除集合索引失败!", e);
		}
	}

	/**
	 * 删除集合索引
	 *
	 * @param collectionName
	 * @param indexName 索引名称
	 * @return
	 */
	public boolean dropIndex(String collectionName, String indexName) {
		if (!collectionExists(collectionName))
			throw new AppException(SysErr.DB_SQL_ERR, collectionName+"不存在.");

		try {
			MongoCollection<Document> collection = getCollectionObject(collectionName);
			collection.dropIndex(indexName);
			return true;
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, collectionName+"删除集合索引失败!", e);
		}
	}

	/**
	 * 集合索引是否存在
	 *
	 * @param collectionName
	 * @param idxName
	 * @return
	 */
	public boolean indexExist(String collectionName, String idxName) {
		if (!collectionExists(collectionName))
			throw new AppException(SysErr.DB_SQL_ERR, collectionName+"不存在.");

		try {
			MongoCollection<Document> collection = getCollectionObject(collectionName);
			ListIndexesIterable<Document> indexs = collection.listIndexes();
			if (indexs==null) return false;

			boolean exist = false;
			MongoCursor<Document> iterator = indexs.iterator();
			while(iterator.hasNext()) {
				String name = JsonTool.getStrValue(toMap(iterator.next()), "name", null);
				if (idxName.equalsIgnoreCase(name)) {
					exist = true;
					break;
				}
			}
			iterator.close();

			return exist;
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, collectionName+"创建集合索引失败!", e);
		}
	}

	///////////////////////////////////////// 更新操作 /////////////////////////////////////////////////

	/**
	 * 根据 filter 过滤条件更新记录
	 *
	 * @param collectionName
	 * @param filter
	 * @param value
	 * @return
	 */
	public UpdateResult updateDocument(String collectionName, Bson filter, Map<String, Object> value) {
		try {
			MongoCollection<Document> collection = this.getCollectionObject(collectionName);

			convertMapId(value);
			Document update = new Document("$set", value);
			UpdateResult ret = collection.updateMany(filter, update);
			return ret;
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, "文档更新失败!", e);
		}
	}

	/**
	 * 按指定条件更新指定结果值
	 * @param collectionName 集合名称
	 * @param cdt 更新条件
	 * @param value 更新值
	 * @return
	 */
	public UpdateResult updateDocument(String collectionName, Map<String, Object> cdt, Map<String, Object> value) {
		try {
			MongoCollection<Document> collection = this.getCollectionObject(collectionName);

			Bson filter = mapToFilter(cdt);

			convertMapId(value);
			Document update = new Document("$set", value);
			return collection.updateMany(filter, update);
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, "文档更新失败!", e);
		}
	}

	/**
	 * 按指定条件更新指定结果值，如果不存在则插入
	 * @param collectionName 集合名称
	 * @param cdt 更新条件
	 * @param value 更新值
	 * @return
	 */
	public UpdateResult upsertDocument(String collectionName, Map<String, Object> cdt, Map<String, Object> value) {
        try {
            MongoCollection<Document> collection = this.getCollectionObject(collectionName);

            Bson filter = mapToFilter(cdt);

            convertMapId(value);
            Document update = new Document("$set", value);
            UpdateOptions updateOptions = new UpdateOptions();
            updateOptions.upsert(true);
            return collection.updateMany(filter, update, updateOptions);
        } catch (Exception e) {
            Log4j2Tool.error(MongoCliDrv.class, e);
            throw new AppException(SysErr.DB_SQL_ERR, "文档更新失败!", e);
        }
    }

	/**
	 * 按指定条件更新指定结果值，如果不存在则插入，批量操作
	 * @param collectionName 集合名称
	 * @param cdt 更新条件
	 * @param value 更新值
	 * @return
	 */
    public BulkWriteResult upsertDocument(String collectionName, List<Map<String, Object>> cdt, List<Map<String,
            Object>> value) {
        try {
            MongoCollection<Document> collection = this.getCollectionObject(collectionName);

            List<WriteModel<Document>> writeModels = new ArrayList<>();
            for (int i = 0; i < cdt.size(); i++) {
                Bson filter = mapToFilter(cdt.get(i));
                Document update = new Document("$set", value.get(i));
                writeModels.add(new UpdateOneModel<>(filter, update, new UpdateOptions().upsert(true)));
            }
            return collection.bulkWrite(writeModels);
        } catch (Exception e) {
            Log4j2Tool.error(MongoCliDrv.class, e);
            throw new AppException(SysErr.DB_SQL_ERR, "文档更新失败!", e);
        }
    }


	/**
	 * 按指定条件更新指定结果值
	 * @param collectionName 集合名称
	 * @param id 更新条件
	 * @param value 更新值
	 * @return
	 */
	public UpdateResult updateDocument(String collectionName, String id, Map<String, Object> value) {
		try {
			MongoCollection<Document> collection = this.getCollectionObject(collectionName);
			Map<String, Object> cdt = new HashMap<>();
			cdt.put("_id", new ObjectId(id));
			Bson filter = mapToFilter(cdt);

			convertMapId(value);
			Document update = new Document("$set",value);
			return collection.updateOne(filter, update);
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, "文档更新失败!", e);
		}
	}

	/**
	 * 按指定条件批量更新指定结果值
	 * @param collectionName 集合名称
	 * @param list 批量写入、删除以及更新的混合体
	 * @return
	 */
	public BulkWriteResult batchWrite(String collectionName, List<? extends WriteModel<? extends Document>> list) {
		try {
			MongoCollection<Document> collection = this.getCollectionObject(collectionName);
			BulkWriteResult rets = collection.bulkWrite(list);
			return rets;
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, "文档批量操作失败!", e);
		}
	}

	////////////////////////////////// 查询功能接口 ////////////////////////////////////////

	/**
	 * 对象转换工具: Document 转 map
	 * @param doc
	 * @return
	 */
	public static Map<String, Object> toMap(Document doc){
		if (doc==null) return null;

		String json = doc.toJson(dataConverter);
		return JsonTool.objectToMap(json, false);
	}

	/**
	 * 对象转换工具: Document转bean对象
	 * @param doc
	 * @param cls
	 * @return
	 */
	public static <T> T toBean(Document doc, Class<T> cls){
		if (doc==null) return null;

		String json = doc.toJson(dataConverter);
		return JsonTool.objectToBean(json, cls);
	}

	/**
	 * 获取记录数
	 * @param collectionName
	 * @param filter
	 * @return
	 */
	public long getRecordCountByBson(String collectionName, Bson filter) {
		try {
			MongoCollection<Document> collection = this.getCollectionObject(collectionName);

			long ret;
			if (filter==null) ret = collection.estimatedDocumentCount();
			else ret = collection.countDocuments(filter);

			return ret;
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, "获取文档记录数失败!", e);
		}
	}

	/**
	 * 获取记录数
	 * @param collectionName
	 * @param filter
	 * @return
	 */
	public long getRecordCountByMap(String collectionName, Map<String, Object> cdt) {
		try {
			Bson filter = this.mapToFilter(cdt);
			return getRecordCountByBson(collectionName, filter);
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, "获取文档记录数失败!", e);
		}
	}

	/**
	 * 根据查询条件查询数据(原生对象查询方式)
	 *
	 * @param collectionName 集合名称
	 * @param filter 过滤条件对象
	 * @param sorts 排序对象
	 * @param cls 返回对象类型
	 * @param page 查询页码(<=0表示不分页)
	 * @param pagesize 页大小(<=0表示获取匹配条件所有记录)
	 * @return
	 */
	public <T>Page<T> queryDocument(String collectionName, Bson filter, List<SortField> sorts, Class<T> cls, int page, int pagesize){
		try {
			MongoCollection<Document> collection = this.getCollectionObject(collectionName);

			Page<T> ret = new Page<>();
			ret.setPageInfo(page, pagesize, 0, 0, null);

			long rows = 0;
			int pages = 1;
			FindIterable<Document> docs = null;
			//查询
			if (filter!=null) {
				if (page>0) rows = collection.countDocuments(filter);
				docs = collection.find(filter);
			}
			else {
				if (page>0) rows = collection.estimatedDocumentCount();
				docs = collection.find();
			}
			if (docs==null) return ret;

			//处理排序
			Bson sort = toQuerySort(sorts);
			if (sort!=null) docs = docs.sort(sort);
			//处理一页数据
			if (pagesize>0) {
				pages = calculatePage(rows, pagesize);

				if (page>0) docs = docs.skip((page-1) * pagesize).limit(pagesize);
				else docs = docs.limit(pagesize);

				if (page>pages) return ret;
			}

			//取出数据
			List<T> data = new ArrayList<>();
			MongoCursor<Document> cursor = docs.iterator();
			//注意: 在大数据情况下，如果页大小大于匹配的记录数，hasNext方法将挂起。正确做法应该传入匹配记录数作为页大小.
			while (cursor.hasNext()) {
				data.add(toBean(cursor.next(), cls));
			}
			cursor.close();
			ret.setPageInfo(page, pagesize, pages, (int)rows, data);
			return ret;
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, "查询文档记录失败!", e);
		}
	}

	/**
	 * 根据(key=value)多条件并关系查询
	 *
	 * @param collectionName
	 * @param cdt
	 * @param cls
	 * @param page
	 * @param pagesize
	 * @return
	 */
	public <T>Page<T> queryDocument(String collectionName, Map<String, Object> cdt, List<SortField> sorts, Class<T> cls, int page, int pagesize){
		Bson filter = this.mapToFilter(cdt);
		return queryDocument(collectionName, filter, sorts, cls, page, pagesize);
	}

	/**
	 * 根据原生态Bson作为条件进行查询
	 * @param collectionName 集合名称
	 * @param filter 查询条件
	 * @return
	 */
	public <T>T getRecord(String collectionName, Bson filter, Class<T> cls){
		Page<T> ret = queryDocument(collectionName, filter, null, cls, 1, 1);
		if (ret==null || ret.getData()==null || ret.getData().size()==0)
			return null;

		try {
			return ret.getData().get(0);
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, "获取文档详情失败!", e);
		}
	}

	/**
	 * 根据(key=value)多条件并关系查询一条记录,若查询条件匹配多记录,则返回一条
	 * @param collectionName 集合名称
	 * @param cdt 查询条件
	 * @return
	 */
	public <T>T getRecord(String collectionName, Map<String, Object> cdt, Class<T> cls){
		Bson filter = this.mapToFilter(cdt);
		Page<T> tmp = queryDocument(collectionName, filter, null, cls, 1, 1);
		if (tmp==null || tmp.getData()==null || tmp.getData().size()==0) return null;

		try {
			return tmp.getData().get(0);
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, "获取文档详情失败!", e);
		}
	}

	/**
	 * 根据ObjectId查询记录
	 * @param collectionName 集合名称
	 * @param id 记录ObjectId
	 * @return
	 */
	public <T>T getRecord(String collectionName, String id, Class<T> cls) {
		Map<String, Object> cdt = new HashMap<>();
		cdt.put("_id", new ObjectId(id));
		return getRecord(collectionName, cdt, cls);
	}

	/**
	 * 聚合查询(通用方法)----------------------------------------------------------------------------------------
	 *
	 * @param collectionName 集合名称
	 * @param pipeline 聚合管道
	 * @param cls 返回类对象
	 * @return
	 */
	public <T> List<T> groupDocument(String collectionName, List<Bson> pipeline, Class<T> cls) {
		try {
			MongoCollection<Document> collection = this.getCollectionObject(collectionName);
			List<T> rets = new ArrayList<>();

			MongoCursor<Document> cursor = collection.aggregate(pipeline).allowDiskUse(true).cursor();
			while (cursor.hasNext()) {
				rets.add(toBean(cursor.next(), cls));
			}

			return rets;
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, "文档聚合查询失败!", e);
		}
	}

	/**
	 * 计算聚合记录数
	 *
	 * @param collection
	 * @param match
	 * @param group
	 * @return
	 */
	private int countOfGroupDocument(MongoCollection<Document> collection, Bson match, Bson group) {
		List<Bson> pipeline = new ArrayList<>();
		//1.搜索条件
		if (match!=null) pipeline.add(match);
		//2.聚合
		if (group!=null) pipeline.add(group);
		//3.合计
		Bson count = new BasicDBObject("$group", new BasicDBObject("rows",new BasicDBObject("$sum",1)).append("_id", null));
		pipeline.add(count);

		MongoCursor<Document> cursor = collection.aggregate(pipeline).cursor();
		while (cursor.hasNext()) {
			Map<String, Object> item = toMap(cursor.next());
			return (int) item.get("rows");
		}

		return 0;
	}

	/**
	 * 聚合查询-------------------------------------------------------------------------------------------------
	 *
	 * @param collectionName 集合名称
	 * @param match 记录匹配条件
	 * @param group 分组
	 * @param project 选择输出项
	 * @param sort 排序
	 * @param page 查询分页
	 * @param pagesize 分页大小
	 * @return
	 */
	public <T> Page<T> groupDocument(String collectionName, Bson match, Bson group, Bson project, List<SortField> sorts, Class<T> cls, int page, int pagesize){
		Page<T> ret = new Page<>();
		ret.setPageInfo(page, pagesize, 0, 0, null);

		try {
			MongoCollection<Document> collection = this.getCollectionObject(collectionName);

			List<Bson> pipeline = new ArrayList<>();
			//1.搜索条件优先
			if (match!=null) pipeline.add(match);
			//2.无聚合作为查询条件
			if (group!=null) pipeline.add(group);
			//3.输出项目
			if (project!=null) pipeline.add(project);
			//4.排序
			Bson sort = toGroupSort(sorts);
			if (sort!=null) pipeline.add(sort);
			//5.分页
			int pages = 0;
			if (page>0 && pagesize>0) {
				int count = countOfGroupDocument(collection, match, group);
				ret.setRows(count);
				pages = calculatePage(count, pagesize);
				ret.setPages(pages);
				if (page>pages) return ret;

				Document skip = new Document("$skip", (page - 1) * pagesize);
				Document limit = new Document("$limit", pagesize);
				pipeline.add(skip);
				pipeline.add(limit);
			} else {
				pages = 1;
			}

			List<T> list = groupDocument(collectionName, pipeline, cls);
			ret.setData(list);

			return ret;
		}catch(Exception e) {
			Log4j2Tool.error(MongoCliDrv.class, e);
			throw new AppException(SysErr.DB_SQL_ERR, "文档分页聚合查询失败!", e);
		}
	}
}
